/* EIX - IPC system
 * Copyright (C) 2008-2011 Jorge Luis Zapata
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library.
 * If not, see <http://www.gnu.org/licenses/>.
 */
#include "Eix.h"
#include "eix_private.h"
/*============================================================================*
 *                                  Local                                     *
 *============================================================================*/
static Eina_List *_servers = NULL;

static Eix_Server * _eix_server_new(Eix_Transport *t,
		Eix_Server_Descriptor *descriptor, void *data)
{
	Eix_Server *thiz;

	thiz = calloc(1, sizeof(Eix_Server));
	thiz->t = t;
	thiz->data = data;
	thiz->descriptor = descriptor;
	thiz->user_data = eina_hash_string_superfast_new(NULL);
	_servers = eina_list_append(_servers, thiz);
	return thiz;
}
/*----------------------------------------------------------------------------*
 *                   The transport process interface                          *
 *----------------------------------------------------------------------------*/
static void _eix_server_transport_server_add(Eix_Transport *other,
		void *user_data)
{
	Eix_Server *thiz = user_data;

	if (!thiz->descriptor || !thiz->descriptor->add) return;
	thiz->descriptor->add(thiz, thiz, thiz->data);
}

static void _eix_server_transport_server_remove(void *user_data)
{
	Eix_Server *thiz = user_data;

	if (thiz->descriptor && thiz->descriptor->del)
		thiz->descriptor->del(thiz, thiz, thiz->data);
	thiz->delete_me = EINA_TRUE;
}

static void _eix_server_transport_server_data(Eix_Message *m, void *body,
		void *reply, Eix_Error *error, void *user_data)
{
	Eix_Server *thiz = user_data;

	if (!thiz->descriptor || !thiz->descriptor->data) return;
	thiz->descriptor->data(thiz, NULL, m, body, reply, error, thiz->data);
}

static Eix_Transport_Process_Descriptor _eix_server_transport_connect_descriptor = {
	/* .add 		= */ _eix_server_transport_server_add,
	/* .remove 		= */ _eix_server_transport_server_remove,
	/* .data		= */ _eix_server_transport_server_data,
};

static void _eix_server_transport_client_data(Eix_Message *m, void *body,
		void *reply, Eix_Error *error, void *user_data)
{
	Eix_Server *thiz;
	Eix_Client *cl = user_data;

	thiz = eix_client_server_get(cl);
	if (!thiz->descriptor || !thiz->descriptor->data) return;
	thiz->descriptor->data(thiz, cl, m, body, reply, error, thiz->data);
}

static void _eix_server_transport_client_remove(void *user_data)
{
	Eix_Server *thiz;
	Eix_Client *cl = user_data;

	thiz = eix_client_server_get(cl);
	if (thiz->descriptor && thiz->descriptor->del)
		thiz->descriptor->del(thiz, cl, thiz->data);
	cl->delete_me = EINA_TRUE;
}

static Eix_Transport_Process_Descriptor _eix_server_transport_client_descriptor = {
	/* .add 	= */ NULL,
	/* .remove 	= */ _eix_server_transport_client_remove,
	/* .data	= */ _eix_server_transport_client_data,
};

static void _eix_server_transport_serve_add(Eix_Transport *other, void *user_data)
{
	Eix_Server *thiz = user_data;
	Eix_Client *cl;

	cl = eix_client_new(other, thiz);
	eix_transport_process_descriptor_set(other,
			&_eix_server_transport_client_descriptor, cl);

	thiz->clients = eina_list_append(thiz->clients, cl);
	if (!thiz->descriptor || !thiz->descriptor->add) return;
	thiz->descriptor->add(thiz, cl, thiz->data);
}

static Eix_Transport_Process_Descriptor _eix_server_transport_serve_descriptor = {
	/* .add 	= */ _eix_server_transport_serve_add,
	/* .remove 	= */ NULL,
	/* .data	= */ NULL,
};
/*============================================================================*
 *                                 Global                                     *
 *============================================================================*/
void eix_server_client_remove(Eix_Server *thiz, Eix_Client *cl)
{
	thiz->clients = eina_list_remove(thiz->clients, cl);
}
/*============================================================================*
 *                                   API                                      *
 *============================================================================*/
/**
 * Connects to an Eix server
 * @param[in] name The name to use for the connection
 * @param[in] port The port to use for the connection
 * @return An Eix server in case the connection was succesfull or NULL
 * otherwise
 */
EAPI Eix_Server * eix_server_connect(Eix_Transport *t,
		Eix_Server_Descriptor *descriptor, void *data)
{
	Eix_Server *thiz;

	if (!t) return NULL;

	thiz = _eix_server_new(t, descriptor, data);
	eix_transport_process_descriptor_set(t,
			&_eix_server_transport_connect_descriptor, thiz);

	if (!eix_transport_connect(t))
	{
		eix_server_del(thiz);
		thiz = NULL;
	}

	return thiz;
}
/**
 * Creates a new Eix server
 * @param[in] name The server's name
 * @param[in] port The server's port to use
 */
EAPI Eix_Server * eix_server_new(Eix_Transport *t,
		Eix_Server_Descriptor *descriptor, void *data)
{
	Eix_Server *thiz;

	if (!t) return NULL;

	thiz = _eix_server_new(t, descriptor, data);
	eix_transport_process_descriptor_set(t,
			&_eix_server_transport_serve_descriptor, thiz);
	if (!eix_transport_serve(t))
	{
		eix_server_del(thiz);
		thiz = NULL;
	}

	return thiz;
}

/**
 * Flushes every message and waits until the server
 * has processed every message
 * @param[in] e Eix The eix connection
 */
EAPI void eix_server_sync(Eix_Server *thiz)
{
	eix_transport_sync(thiz->t);
}

/**
 *
 */
EAPI void eix_server_data_set(Eix_Server *thiz, const char *str, void *data)
{
	if (!thiz) return;
	eina_hash_add(thiz->user_data, str, data);
}

/**
 *
 */
EAPI void * eix_server_data_get(Eix_Server *thiz, const char *str)
{
	if (!thiz) return NULL;
	return eina_hash_find(thiz->user_data, str);
}

/**
 *
 */
EAPI void eix_server_del(Eix_Server *thiz)
{
	Eix_Client *c;

	if (!thiz) return;

	EINA_LIST_FREE(thiz->clients, c)
	{
		eix_client_del(c);
	}
	eix_transport_process_descriptor_set(thiz->t, NULL, NULL);
	eix_transport_free(thiz->t);
	free(thiz);
}

/**
 *
 */
EAPI Eina_Bool eix_server_message_send(Eix_Server *thiz, Eix_Message *m,
		void *data, void *rdata, Eix_Error *err, uint32_t *id)
{
	if (!thiz) return EINA_FALSE;
	if (!thiz->t) return EINA_FALSE;

	return eix_transport_send(thiz->t, m, 0, data, rdata, err, id, NULL, NULL);
}

/**
 *
 */
EAPI Eina_Bool eix_server_message_send_deferred(Eix_Server *thiz, Eix_Message *m,
		void *data, Eix_Transport_Reply_Cb cb, const void *user_data)
{
	if (!thiz) return EINA_FALSE;
	if (!thiz->t) return EINA_FALSE;

	return eix_transport_send(thiz->t, m, 0, data, NULL, NULL, NULL, cb, user_data);
}

/**
 *
 */
EAPI const Eina_List * eix_server_client_get(Eix_Server *thiz)
{
	if (!thiz) return NULL;
	return thiz->clients;
}

/**
 *
 */
EAPI Eix_Transport * eix_server_transport_get(Eix_Server *thiz)
{
	return thiz->t;
}

/**
 * TODO we need to do this instead of the transport process
 * for better handling of errors on the processing, we need toplevel
 * call
 */
EAPI void eix_server_process(Eix_Server *thiz, Eina_Bool can_read, Eina_Bool can_write)
{
       	eix_transport_process(thiz->t, can_read, can_write);
	if (thiz->delete_me)
		eix_server_del(thiz);
}
